﻿using System;
using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Unity;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Practices.SharePoint.Common.ServiceLocation;
using Microsoft.Practices.Unity;
using Microsoft.SharePoint;
using SPFluid.Core.Modularity;
using SPFluid.Core.Validation;
using SPFluid.Core.Validation.Extensions;

namespace SPFluid.Core.ServiceLocation
{
    public class FluidServiceLocator
    {
        private static readonly Dictionary<Guid, SiteLocatorEntry> SiteLocators = new Dictionary<Guid, SiteLocatorEntry>();

        private static readonly object SyncRoot = new object();

        private static volatile FluidServiceLocator instance;

        private static int siteCacheInterval = 60;

        public static int SiteCachingTimeoutInSeconds
        {
            get
            {
                return siteCacheInterval;
            }

            set
            {
                Guard.That(() => value).IsGreater(0);

                siteCacheInterval = value;
            }
        }

        private static FluidServiceLocator Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (SyncRoot)
                    {
                        if (instance == null)
                        {
                            instance = new FluidServiceLocator();
                        }
                    }
                }

                return instance;
            }
        }

        public static IServiceLocator GetCurrent()
        {
            return Instance.DoGetCurrent();
        }

        public static IServiceLocator GetCurrent(SPSite site)
        {
            Guard.That(() => site).IsNotNull();
            
            return Instance.DoGetCurrent(site);
        }

        public static void Reset()
        {
            lock (SyncRoot)
            {
                SiteLocators.Clear();
            }
        }

        private IServiceLocator DoGetCurrent()
        {
            return this.DoGetCurrent(SPContext.Current.Site);
        }

        private IServiceLocator DoGetCurrent(SPSite site)
        {
            IServiceLocator locator;

            if (SiteLocators.ContainsKey(site.ID))
            {
                var entry = SiteLocators[site.ID];

                if (DateTime.Now.Subtract(entry.LoadTime).TotalSeconds < SiteCachingTimeoutInSeconds)
                {
                    locator = SiteLocators[site.ID].Locator;
                }
                else
                {
                    locator = this.CreateServiceLocatorInstance(site);
                }
            }
            else
            {
                locator = this.CreateServiceLocatorInstance(site);
            }

            return locator;
        }

        private IServiceLocator CreateServiceLocatorInstance(SPSite site)
        {
            var container = this.CreateUnityContainerInstance(site);
            var serviceLocator = new UnityServiceLocator(container);

            var entry = new SiteLocatorEntry();
            entry.Locator = serviceLocator;
            entry.LoadTime = DateTime.Now;

            lock (SyncRoot)
            {
                SiteLocators[site.ID] = entry;
            }

            return serviceLocator;
        }

        private IUnityContainer CreateUnityContainerInstance(SPSite site)
        {
            var unityContainer = new UnityContainer();

            this.SetupEnterpriseLibrary(unityContainer);
            this.SetupModules(unityContainer, site);

            return unityContainer;
        }

        private void SetupEnterpriseLibrary(IUnityContainer unityContainer)
        {
            var builder = new ConfigurationSourceBuilder();
            builder.ConfigureCaching().ForCacheManagerNamed(Constants.EnterpriseCachingName).UseAsDefaultCache().StoreInMemory();

            var configSource = new DictionaryConfigurationSource();
            builder.UpdateConfigurationWithReplace(configSource);

            unityContainer.AddExtension(new EnterpriseLibraryCoreExtension(configSource));
        }

        private void SetupModules(IUnityContainer unityContainer, SPSite site)
        {
            var spgServiceLocator = SharePointServiceLocator.GetCurrent(site);
            var modules = spgServiceLocator.GetAllInstances<IModule>();
            foreach (var module in modules)
            {
                module.Setup(unityContainer);
            }
        }
    }
}